/*
 * bam.cpp
 *
 *  Created on: Aug 25, 2011
 *      Author: lindenb
 */
#include <stdexcept>
#include <cerrno>
#include <alloca.h>
#include "xzlib.h"
#include "bam.h"
#include "xstdlib.h"
#include "xstring.h"
#include "xexception.h"

using namespace std;


Reference::Reference():_name(NULL),_id(0),_len(0)
    {
    }

Reference::Reference(int32_t id,const char* name,int32_t len):
	_name(::safeStrdup(name)),_id(id),_len(len)
    {

    }

Reference::~Reference()
    {
    free(_name);
    }

int32_t Reference::id() const
    {
    return _id;
    }

int32_t Reference::length() const
    {
    return _len;
    }

const char* Reference::name() const
    {
    return _name;
    }


BamReader::BamReader(const char* filename):bgz(NULL),text(NULL),n_ref(0),references(NULL)
    {
    errno=0;
    bgz=(void*)safeGZOpen(filename,"r");
    char magic[4];
    if(::gzread(((gzFile)bgz),magic,4)!=4)
	{
	THROW("In "<< filename << ": cannot read magic header.");
	}

    if(	magic[0]!='B' || magic[1]!='A' ||
	magic[2]!='M' || magic[3]!='\1')
	{
	THROW("In "<< filename << ": wrong magic header. "<<
		"Expected \"BAM\\1\"");
	}
    int32_t l_text;
    if(::gzread(((gzFile)bgz),&l_text,sizeof(int32_t))!=sizeof(int32_t))
    	{
    	THROW("In "<< filename << ": cannot read l_text");
    	}
    this->text=(char*)safeMalloc(l_text+1);
    if(::gzread(((gzFile)bgz),this->text,l_text)!=l_text)
	{
	THROW("In "<< filename << ": cannot read text ("<< l_text << " bytes)");
	}

    this->text[l_text]=0;

    if(::gzread(((gzFile)bgz),&n_ref,sizeof(int32_t))!=sizeof(int32_t))
	{
	THROW("In "<< filename << ": cannot read n_ref.");
	}

    references=(Reference**)safeMalloc(n_ref*sizeof(Reference*));

    for(int32_t i=0;i< n_ref;++i)
	{
	int32_t l_name;
	if(::gzread(((gzFile)bgz),&l_name,sizeof(int32_t))!=sizeof(int32_t))
	    {
	    THROW("In "<< filename << ": cannot read l_name.");
	    }
	if(l_name<2)
	    {
	    THROW("In "<< filename << ": got l_name < 2.");
	    }
	char* refname=(char*)safeMalloc(l_name);
	if(::gzread(((gzFile)bgz),refname,l_name)!=l_name)
	    {
	    THROW("In "<< filename << ": cannot read refName ("<< l_name << " bytes)");
	    }
	if(refname[l_name-1]!=0)
	    {
	    THROW("No \\0 at the end of the reference name.");
	    }
	int32_t seqLen;
	if(::gzread(((gzFile)bgz),&seqLen,sizeof(int32_t))!=sizeof(int32_t))
	    {
	    THROW("In "<< filename << ": cannot read seqLen.");
	    }
	references[i]=new Reference(i,refname,seqLen);

	cerr << references[i]->name() << endl;

	free(refname);
	}
    _align=new BamAlign;
    }

BamReader::~BamReader()
    {
    ::gzclose((gzFile)bgz);
    free(text);
    for(int32_t i=0;i< n_ref;++i)
	{
	delete references[i];
	}
    free(references);
    delete _align;
    }

const BamAlign* BamReader::next()
    {
    int32_t block_len;
    uint32_t tmp[8];

    if(::gzread(((gzFile)bgz),&block_len,sizeof(int32_t))!=sizeof(int32_t))
    	{
	/* EOF */
    	return NULL;
    	}

    if (::gzread(((gzFile)bgz), tmp, 8*sizeof(uint32_t)) != 8*sizeof(uint32_t))
	{
	THROW("cannot read "<< 8*sizeof(uint32_t)<< " bytes");
	}
  // c=_align
    _align->tid = tmp[0];
    if(_align->tid<-1 || _align->tid>= this->n_ref)
	{
	THROW("tid out of range:"<< _align->tid);
	}
    _align->pos = tmp[1];
    _align->bin = tmp[2]>>16;
    _align->qual = tmp[2]>>8&0xff;
    _align->l_qname = tmp[2]&0xff;
    _align->flag = tmp[3]>>16;
    _align->n_cigar = tmp[3]&0xffff;
    _align->l_qseq = tmp[4];
    _align->mtid = tmp[5];
    _align->mpos = tmp[6];
    _align->isize = tmp[7];
    _align->data_len = block_len - (8*sizeof(uint32_t));
    if(_align->m_data < _align->data_len)
		{
		_align->m_data = _align->data_len;
		//kroundup32(b->m_data);
		_align->data = (uint8_t*)safeRealloc(_align->data, _align->m_data);
		}
    if (gzread((gzFile)bgz, _align->data, _align->data_len) != _align->data_len)
	{
	THROW("Cannot read data");
	}
    _align->l_aux = _align->data_len - _align->n_cigar * 4 - _align->l_qname - _align->l_qseq - (_align->l_qseq+1)/2;
    cerr << "OK\n";
    cerr.write((char*)_align->data,_align->l_qname);
    cerr << "\n";
    return _align;
    }

int main(int argc,char** argv)
    {
    for(int i=1;i< argc;++i)
	{
	const BamAlign* aln=NULL;
	BamReader* r=new BamReader(argv[i]);
	while((aln=r->next())!=NULL)
	    {
	    cerr << "next\n";
	    }
	delete r;
	}
    return 0;
    }
